package gxprod.androidai.support;


import java.util.ArrayList;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.math.BigDecimal;
import java.math.MathContext;

/**
 * Solves simple and complex equations.
 * @author Josh Maleszewski
 * 
 * TODO: Add Trigonometry functions: 				sin(), 		cos(), 		tan()
 * TODO: Add Invers Trigonometry functions: 		sec(), 		csc(), 		cot()
 * TODO: Add Arc Trigonometry functions: 			asin(), 	acos(), 	atan()
 * TODO: Add Arc Inverse Trigonometry functions: 	asec(), 	acsc(), 	acot()
 * TODO: Add Logarimitic Function: 					ln, 		log2,  		log[base](equ)
 * TODO: Add absolute value function 				|  |, 		abs()
 * TODO: Add rounding functions 					int(), 		round(a,b)
 */
public final class EquationSolver {

	/*Constants*/
	private static final Character POW = new Character('^');
	private static final Character MUL = new Character('*');
	private static final Character DIV = new Character('/');
	private static final Character MOD = new Character('%');
	private static final Character ADD = new Character('+');
	private static final Character[] firstSet = {POW};
	private static final Character[] secondSet = {MUL, DIV, MOD};
	private static final Character[] thirdSet = {ADD};
	private static final DecimalFormat DF = new DecimalFormat();
	private static final StringBuffer SB = new StringBuffer();
	private static final MathContext MC = new MathContext(40);
	private enum Direction {
		L_TO_R,
		R_TO_L
	};

	private static final String[] Functions = { 
		"sin",	"cos",	"tan",
		"sec",	"csc",	"cot",
		"asin",	"acos",	"atan",
		"asec",	"acsc",	"acot",
		"ln",	"log",	"log2",	"log10",
		"abs",	
		"int", "floor", "flr", "ceil",
		"sqrt", "cbrt"
	};


	private static String doOperation(String lhs, char operator, String rhs){
		BigDecimal bdLhs = new BigDecimal(lhs);
		BigDecimal bdRhs = new BigDecimal(rhs);
		switch(operator){
			case '^':
				return "" + Math.pow( bdLhs.doubleValue(), bdRhs.doubleValue() );
			case '*':
				return "" + bdLhs.multiply(bdRhs).toString();
			case '/':
				return "" + bdLhs.divide(bdRhs, MC).toString();
			case '+':
				return "" + bdLhs.add(bdRhs).toString();
			case '%':
				return "" + bdLhs.remainder(bdRhs).toString();
		}
		return "";
	}

	/**
	 * Returns a corrected version of the String, which is one that
	 * has its first and last paranthesis removed.
	 */
	private static String correctedString(String arg){

		StringBuilder sb = new StringBuilder(); // A Mutable String
		boolean foundFirst = false;
		for(int i = 0; i < arg.length(); i++)
			if(foundFirst == false && arg.charAt(i) == '(')
				foundFirst = true;
			else sb.append(arg.charAt(i));

		arg = new StringBuilder(sb.reverse()).toString();
		sb.delete(0, sb.length());

		foundFirst = false;
		for(int i = 0; i < arg.length(); i++)
			if(foundFirst == false && arg.charAt(i) == ')')
				foundFirst = true;
			else sb.append(arg.charAt(i));

		return arg = new StringBuilder(sb.reverse()).toString();
	}

	/**
	 * Returns the a String that all of the characters the parameter argu
	 * has, minus the space characters in the String
	 */
	private static String removeSpaces(String argu){
		String temp = "";

		for(int i = 0; i < argu.length(); i++)
			if(argu.charAt(i) != ' ')
				temp += "" + argu.charAt(i);

		return temp;
	}


	private static String parse(String arg){

		String expression = removeSpaces(correctedString(arg));
		String finalExpression = "";
		boolean operatorEncountered = true;
		boolean initialValue = true;
		for(int i = 0; i < expression.length(); i++){
			if(expression.charAt(i) == '('){
				String multiply = "";
				if(operatorEncountered == false && initialValue == false){
					multiply += "*";
				}

				String placeHolder = "(";
				int valuesCounted = 1;
				operatorEncountered = false;
				for(int j = i + 1; valuesCounted != 0; j++){
					if(expression.charAt(j) == '(')
						valuesCounted++;
					else if(expression.charAt(j) == ')')
						valuesCounted--;
					placeHolder += "" + expression.charAt(j);
				}

				String evaluatedString = parse(placeHolder);

				//	-------------
				//  -------------
				//  -------------
				// XXX: possible function insertion point

				int funcMatch = -1;
				for(int f = 0; f < Functions.length; f++) {
					String func = Functions[f];
					if(i >= func.length()) {
						String stub = expression.substring(i - func.length(), i);
						if(stub.equalsIgnoreCase(func)) {
							funcMatch = f;
							break;
						}
					}
				}

				if(funcMatch >= 0) {
					evaluatedString = Double.toString(executeFunction(new Double(evaluatedString), funcMatch));

					switch(Functions[funcMatch].length()) {
						case 2:
							
							
						case 3:
							
							
						case 4:
							
							
						case 5:
							
							
					}
				}

				//  -------------
				//  -------------
				//  -------------

				finalExpression += multiply + evaluatedString;
				i+= (placeHolder.length() - 1);
			}else{
				if(expression.charAt(i) == '-' && operatorEncountered == false){
					finalExpression += ((!initialValue) ? "+": "") + expression.charAt(i);
				}else if(expression.charAt(i) == '-' && operatorEncountered == true){
					finalExpression += "-1*";
				}else finalExpression += expression.charAt(i);

				if((expression.charAt(i) == '+'
						|| expression.charAt(i) == '/'
						|| expression.charAt(i) == '^'
						|| expression.charAt(i) == '*'
						|| expression.charAt(i) == '%'
						|| expression.charAt(i) == '-'))
					operatorEncountered = true;
				else if(expression.charAt(i) != ' ')
					operatorEncountered = false;
			}
			initialValue = false;
		}

		finalExpression = removeSpaces(finalExpression);
		String perfectExpression = "";

		for(int i = 0; i < finalExpression.length(); i++){
			if((i + 1) < finalExpression.length())
				if(finalExpression.charAt(i) == '-' && finalExpression.charAt(i + 1) == '-')
					i+=2;
			perfectExpression += "" + finalExpression.charAt(i);
		}
		finalExpression = perfectExpression;

		ArrayList<String> totalNumbers = new ArrayList<String>(0);
		ArrayList<Character> totalOperations = new ArrayList<Character>(0);
		System.out.println(finalExpression);

		for(int i = 0; i < finalExpression.length(); i++){
			if(finalExpression.charAt(i) >= '0' && finalExpression.charAt(i) <= '9'
					|| finalExpression.charAt(i) == '-' || finalExpression.charAt(i) == '.'
					|| finalExpression.charAt(i) == ','){
				String temp = "";
				for(int j = i; j < finalExpression.length(); j++){
					if(finalExpression.charAt(j) >= '0' && finalExpression.charAt(j) <= '9'
							|| finalExpression.charAt(j) == '-' || finalExpression.charAt(j) == '.'
							|| finalExpression.charAt(j) == ','){
						temp += "" + finalExpression.charAt(j);
					}else break;
				}
				totalNumbers.add(temp);
				i += temp.length() == 0 ? 0 : (temp.length() - 1);
			}else if(finalExpression.charAt(i) == '*'
					|| finalExpression.charAt(i) == '/'
					|| finalExpression.charAt(i) == '^'
					|| finalExpression.charAt(i) == '+'
					|| finalExpression.charAt(i) == '%'
					){
				totalOperations.add(new Character(finalExpression.charAt(i)));
			}
		}

		calculate(totalNumbers, totalOperations, firstSet, Direction.R_TO_L);
		calculate(totalNumbers, totalOperations, secondSet, Direction.L_TO_R);
		calculate(totalNumbers, totalOperations, thirdSet, Direction.L_TO_R);

		return totalNumbers.get(0);
	}

	private static double executeFunction(double value, int funcMatch) {

		switch(funcMatch) {
			case 0: return Math.sin(value);					// sine
			case 1: return Math.cos(value);					// cosine
			case 2: return Math.tan(value);					// tangent

			case 3: return 1.0 / Math.sin(value);			// secent
			case 4: return 1.0 / Math.cos(value);			// csc
			case 5: return 1.0 / Math.tan(value);			// cotangent

			case 6: return Math.asin(value);					// arc sine
			case 7: return Math.acos(value);					// arc cosine
			case 8: return Math.atan(value);					// arc tangent

			case 9: return 1.0 / Math.asin(value);			// arc secent
			case 10: return 1.0 / Math.acos(value);			// arc csc
			case 11: return 1.0 / Math.atan(value);			// arc cotangent

			case 12: return Math.log(value);				// ln
			case 13: return Math.log10(value);				// log 10
			case 14: return Math.log(value) / Math.log(2);	// log 2
			case 15: return Math.log10(value);				// log 10

			case 16: return Math.abs(value);				// sine

			case 17: return Math.round(value);				// int
			case 18:										// floor
			case 19: return Math.floor(value);				// flr
			case 20: return Math.ceil(value);				// ceil

			case 21: return Math.sqrt(value);
			case 22: return Math.cbrt(value);


		}


		return 0.0;
	}

	/**
	 * Returns true if the target character exists in the set of Character operands, returns false otherwise.
	 */
	private static boolean containsCharacter(Character anOperation, Character operands[]){
		for(Character item : operands){
			if(anOperation.equals(item)){
				return true;
			}
		}
		return false;
	}

	/**
	 * Attempts to solve an equation that is seperated into a set of numbers and operands.
	 * (More to add)
	 */
	private static void calculate(ArrayList<String> totalNumbers, ArrayList<Character> totalOperations, Character operands[], Direction dir){
		String result = "";
		if(dir == Direction.L_TO_R){
			for(int i = 0; i < totalOperations.size(); i++){
				if(containsCharacter(totalOperations.get(i), operands)){
					result = doOperation(totalNumbers.get(i), (char)totalOperations.get(i), totalNumbers.get(i + 1));
					totalNumbers.set(i, result);
					totalOperations.remove(i);
					totalNumbers.remove(i + 1);
					i--;
				}
			}
		}else if(dir == Direction.R_TO_L){
			for(int i = totalOperations.size() - 1; i >= 0; i--){
				if(containsCharacter(totalOperations.get(i), operands)){
					result = doOperation(totalNumbers.get(i), (char)totalOperations.get(i), totalNumbers.get(i + 1));
					totalNumbers.set(i, result);
					totalOperations.remove(i);
					totalNumbers.remove(i + 1);
				}
			}
		}
	}

	/**
	 * Checks to see if the expression is solvable or not.
	 *
	 * This method is actually a misnomer, because more restrictions
	 * should be put in place on what a user can determine as solvable.
	 */
	private static boolean isSolvable(String eq){

		int paranthesisCount = 0;
		for(char element : eq.toCharArray()){
			if(element == '(')
				paranthesisCount++;
			else if(element == ')')
				paranthesisCount--;

			if(paranthesisCount < 0)
				return false;
		}
		return paranthesisCount == 0;
	}

	/**
	 * Attempts to solve an equation
	 */
	public static String solve(String eq){
		if(isSolvable(eq)){
			System.out.println(eq);
			String value = "(" + eq + ")";
			return parse(value);
		} else
			return "";
	}

	/**
	 * Attempts to solve an equation, with the precision factor taken into account.
	 *
	 * The maximum precision is 40, only because the max precision for the MathContext object is 40
	 * though this is not required and can be changed in future versions.
	 */
	public static String solve(String eq, int precision){
		SB.delete(0, SB.length());
		return DF.format(
				(double)Double.parseDouble(solve(eq)),
				SB,
				new FieldPosition(precision)).toString();
	}
}